/*
 * Copyright (c) TD-Tech Technologies Co., Ltd. 2024-2024. All rights reserved.
 */

import hilog from '@ohos.hilog';

const SYMBOL = ' --> ';

export enum LogDomain {
  TdSystemManager = 0x02b00, // main tag
  TdSM_LogCollect = 0x02b01, // log collect
  TdSM_LogSwitchControl = 0x02b02, // log switch control
  TdSM_AutoStartup = 0x02b03, // autostart
  TdSM_DevicePolicy = 0x02b04, // device policy
  DEBUG_MAIN = 0x02b05, // main tag
}

export const enum DomainName {
  TDSM = 'TdSystemManager', // main tag
  LOGCOLLECT = 'TdSM_LogCollect', // log grab
  LOGSWITCHCONTROL = 'TdSM_LogSwitchControl', // log switch control
  AutoStartup = 'TdSM_AutoStartup', // autostart
  DevicePolicy = 'TdSM_DevicePolicy', // device policy
  DEBUG = 'DEBUG_Main', // main tag
};

/**
 * Whether enables logs of each domain, used for debugging
 */
const LOG_ENABLE_MAP: Map<LogDomain, boolean> = new Map([
  [LogDomain.TdSystemManager, true],
  [LogDomain.TdSM_LogCollect, true],
  [LogDomain.TdSM_LogSwitchControl, true],
  [LogDomain.TdSM_AutoStartup, true],
  [LogDomain.TdSM_DevicePolicy, true],
  [LogDomain.DEBUG_MAIN, true],
]);

/**
 * log util class
 */
export class LogHelper {
  domain: number;
  domainName: string;
  tag: string;

  private static instanceMap: Map<string, LogHelper> = new Map();
  private static isLoggableMap: Map<string, boolean> = new Map();

  private constructor(domain: LogDomain, tag: string) {
    this.domain = domain;
    for (const [key, value] of Object.entries(LogDomain)) {
      if (value === domain) {
        this.domainName = key;
        break;
      }
    }
    this.tag = tag;
  }

  /**
   * get LogHelper instance.
   *
   * @param domain Indicates the log domain.
   * @param tag Indicates the log tag.
   */
  public static getLogHelper(domain: LogDomain, tag: string): LogHelper {
    const key = `${domain}-${tag}}`;
    if (!LogHelper.instanceMap.has(key)) {
      LogHelper.instanceMap.set(key, new LogHelper(domain, tag));
    }
    return LogHelper.instanceMap.get(key)!;
  }

  /**
   * Outputs info-level logs.
   *
   * @param format Indicates the log format string.
   * @param args Indicates the log parameters.
   */
  public showInfo(format: string, ...args: any[]): void {
    if (this.isLoggable(hilog.LogLevel.INFO)) {
      hilog.info(this.domain, this.domainName, this.tag + SYMBOL + format, args);
    }
  }

  /**
   * Outputs debug-level logs.
   *
   * @param format Indicates the log format string.
   * @param args Indicates the log parameters.
   */
  public showDebug(format: string, ...args: any[]): void {
    if (this.isLoggable(hilog.LogLevel.DEBUG)) {
      hilog.debug(this.domain, this.domainName, this.tag + SYMBOL + format, args);
    }
  }

  /**
   * Outputs warning-level logs.
   *
   * @param format Indicates the log format string.
   * @param args Indicates the log parameters.
   */
  public showWarn(format: string, ...args: any[]): void {
    if (this.isLoggable(hilog.LogLevel.WARN)) {
      hilog.warn(this.domain, this.domainName, this.tag + SYMBOL + format, args);
    }
  }

  /**
   * Outputs error-level logs.
   *
   * @param format Indicates the log format string.
   * @param args Indicates the log parameters.
   */
  public showError(format: string, ...args: any[]): void {
    if (this.isLoggable(hilog.LogLevel.ERROR)) {
      hilog.error(this.domain, this.domainName, this.tag + SYMBOL + format, args);
    }
  }

  /**
   * Outputs fatal-level logs.
   *
   * @param format Indicates the log format string.
   * @param args Indicates the log parameters.
   */
  public showFatal(format: string, ...args: any[]): void {
    if (this.isLoggable(hilog.LogLevel.FATAL)) {
      hilog.fatal(this.domain, this.domainName, this.tag + SYMBOL + format, args);
    }
  }

  /**
   * Outputs debug-level logs.
   *
   * @param logs Indicates the log content, support any value.
   */
  public debug(...logs: unknown[]): void {
    this.showDebug(this.format(logs));
  }

  /**
   * Outputs info-level logs.
   *
   * @param logs Indicates the log content, support any value.
   */
  public info(...logs: unknown[]): void {
    this.showInfo(this.format(logs));
  }

  /**
   * Outputs warn-level logs.
   *
   * @param logs Indicates the log content, support any value.
   */
  public warn(...logs: unknown[]): void {
    this.showWarn(this.format(logs));
  }

  /**
   * Outputs error-level logs.
   *
   * @param logs Indicates the log content, support any value.
   */
  public error(...logs: unknown[]): void {
    this.showError(this.format(logs));
  }

  /**
   * Outputs fatal-level logs.
   *
   * @param logs Indicates the log content, support any value.
   */
  public fatal(...logs: unknown[]): void {
    this.showFatal(this.format(logs));
  }

  /**
   * Checks whether logs of the specified tag, and level can be printed.
   *
   * @param level log level
   */
  public isLoggable(level: hilog.LogLevel): boolean {
    if (!LOG_ENABLE_MAP.has(this.domain)) {
      return false;
    }
    let key: string = `${this.domain}-${level}`;
    if (!LogHelper.isLoggableMap.has(key)) {
      LogHelper.isLoggableMap.set(key, hilog.isLoggable(this.domain, this.domainName, level));
    }
    return LogHelper.isLoggableMap.get(key)!;
  }

  /**
   * Format log content
   */
  private format(logs: unknown[]): string {
    const message = logs.map((log) => {
      try {
        if (typeof log === 'string') {
          return log;
        }

        if (log instanceof Error) {
          let errorMessage = '';
          if (Object.prototype.hasOwnProperty.call(log, 'code')) {
            const code = (log as Error & { code: number }).code;
            errorMessage += code !== undefined ? `[${code}]` : '';
          }
          errorMessage += log.message;

          return errorMessage + (log.stack ? '\n' + log.stack : '');
        }

        return JSON.stringify(log);
      } catch {
        return log;
      }
    }).join(' ');

    return message;
  }
}